package com.naiterui.ehp.bs.doctor.service.impl;

import java.util.Date;
import java.util.Map;
import java.util.concurrent.Executor;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.naiterui.common.redis.RedisUtil;
import com.naiterui.ehp.bp.bo.doctor.DoctorAuditParamBO;
import com.naiterui.ehp.bp.bo.doctor.RecordParamAuditBO;
import com.naiterui.ehp.bp.bo.esign.NoSecretParamBO;
import com.naiterui.ehp.bp.bo.esign.PreSignResultBO;
import com.naiterui.ehp.bp.constants.CommonConstant;
import com.naiterui.ehp.bp.constants.DoctorEsignStatusEnum;
import com.naiterui.ehp.bp.domain.AuditLogs;
import com.naiterui.ehp.bp.domain.Doctor;
import com.naiterui.ehp.bp.domain.DrAccountOperateLog;
import com.naiterui.ehp.bp.domain.Licence;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.utils.beans.BeanUtil;
import com.naiterui.ehp.bp.utils.lang.ConfigUtil;
import com.naiterui.ehp.bs.doctor.dao.IAuditLogDao;
import com.naiterui.ehp.bs.doctor.dao.IDoctorDao;
import com.naiterui.ehp.bs.doctor.dao.IDrAccountOperateLogDao;
import com.naiterui.ehp.bs.doctor.dao.user.ILicenceDao;
import com.naiterui.ehp.bs.doctor.exception.ExceptionCodes;
import com.naiterui.ehp.bs.doctor.fegin.EsignFeginClient;
import com.naiterui.ehp.bs.doctor.service.IAuditService;
import com.naiterui.ehp.bs.doctor.service.notify.INotifyService;
import com.naiterui.ehp.bs.doctor.utils.PropertyValueConstants;
import com.naiterui.ehp.bs.doctor.utils.RedisUtils;
import com.naiterui.ehp.bs.doctor.utils.SMSUtil;

import cn.hutool.core.util.IdcardUtil;


@Service
public class AdAuditServiceImpl implements IAuditService {

    private static final Logger LOGGER = LoggerFactory.getLogger(AdAuditServiceImpl.class);

    private static final Integer ESIGN_AUTH_STATUS_NOTPASS = 2;

    public static final String HOSPITAL_NAME = ConfigUtil.getString("ehp.hospital.name");

    @Autowired
    private IDoctorDao doctorDao;
    @Autowired
    private IAuditLogDao auditLogDao;
    @Autowired
    private IDrAccountOperateLogDao accountOperateLogDao;
    @Autowired
    private ILicenceDao licenceDao;
    @Autowired
    private INotifyService notifyService;
    @Autowired
    private Executor taskExecutor;
    @Value("${file.base.prefix}")
    private String fileBasePath;

    @Autowired
    private EsignFeginClient esignFeginClient;


    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateAuthAudit(String operator, DoctorAuditParamBO audit) throws BusinessException {
        Long doctorId = audit.getDoctorId();
        Doctor doctor = this.getDoctor(doctorId);
        Integer status = doctor.getStatus();

        // 审核通过
        if (Doctor.AUTH_STATUS_SUCCESS == status) {
            throw new BusinessException(ExceptionCodes.DOCTOR_AUDIT_ADOPT);
        }

        Integer auditStatus = audit.getStatus();

        // 如果审核通过则校验认证资质
        if (Doctor.AUTH_STATUS_SUCCESS == auditStatus) {
            LOGGER.warn("医生认证资质校验 ID:{}", doctorId);
            this.validate(doctorId, Licence.DOCTOR_LICENCE_TYPE_AVATAR, Licence.DOCTOR_LICENCE_TYPE_EMCARD, Licence.DOCTOR_LICENCE_TYPE_MEDICAL);
            doctor.setRecordStatus(2);
        }
        // 如果审核不通过 备案同步改为不通过, 实名状态变成未实名
        if (Doctor.AUTH_STATUS_REJECTED == auditStatus) {
            LOGGER.warn("医生认证审核不通过 ID:{}", doctorId);
            doctor.setRecordStatus(3);
            esignFeginClient.setAuthStatus(doctorId, DoctorEsignStatusEnum.AUTH_STATUS_WAIT_AUDIT.getStatus());
            RedisUtils.delete(CommonConstant.REDIS_PRE_ESIGN + "signPwd_lasted" + doctor.getPhone());
        }

        // 认证审核日志更新
        AuditLogs auditLogs = new AuditLogs();
        BeanUtil.copyProperties(audit, auditLogs);
        Date nowTime = new Date();
        auditLogs.setCreatedBy(operator);
        auditLogs.setChangedBy(operator);
        auditLogs.setChangedAt(nowTime);
        auditLogs.setCreatedAt(nowTime);
        auditLogDao.save(auditLogs);

        // 医生审核状态更新
        doctor.setStatus(auditStatus);
        doctor.setChangedAt(nowTime);
        doctorDao.update(doctor);
        LOGGER.info("更新医生:{} 认证审核状态:{}", doctorId, auditStatus);

        // 医生端1.1优化去掉备案状态审核，认证审核通过直接创建ca账户 2021-06-01 by miaohui
        // 认证通过调用以前的备案接口
        Licence licence = licenceDao.getLicenceByType(doctorId, Licence.DOCTOR_LICENCE_TYPE_IDCARD);
        // 判断是否已经注册过CA账户
        Boolean haveCaAccount = esignFeginClient.getAccountByDoctorIdAndIdNo(doctorId,licence.getNumber());
        LOGGER.info("判断是否已经注册过CA账户  doctorId={}, haveCaAccount={}", doctorId, haveCaAccount);
        // 备案审核通过，通知电子签名服务注册医生CA账户
        if (Doctor.AUTH_STATUS_SUCCESS == auditStatus && !haveCaAccount) {
            String pwd = licence.getNumber().substring(licence.getNumber().length() - 4).toUpperCase();
            esignFeginClient.esignDoctorAdd(doctorId, doctor.getName(),
                    licence.getNumber(), doctor.getPhone(), HOSPITAL_NAME, doctor.getSealImage(), pwd);
            //更新recom_esign_doctor认证状态为通过（以前逻辑为修改签名密成功后更新状态，1.1优化后去掉了修改签名密码，改为认证通过默认设置免密，（身份证后四位））
            // esignFeginClient.setAuthStatus(doctorId, DoctorEsignStatusEnum.AUTH_STATUS_PASS.getStatus());
            //默认设置免密，身份证后四位
            // esignFeginClient.setNoSecret(doctorId, true, pwd);

            // 集成默认设置免密，身份证后四位
            // this.setNoSecret(doctorId, pwd);
        }
        // 添加操作日志
        this.addRecordLog(doctorId, auditStatus, operator, audit.getFailureReason());


        // 异步通知
        taskExecutor.execute(() -> {
            try {
                // 发送认证消息：push和系统消息
                notifyService.notifyAuthStatusChange(doctorId, auditStatus, audit.getFailureReason());
                // 审核成功发送系统消息
                if (Doctor.AUTH_STATUS_SUCCESS == auditStatus) {
                    SMSUtil.asyncSendSMS(doctor.getPhone(), SMSUtil.Template.AUDIT_PASS_TEMPLATE);
                } else if (Doctor.AUTH_STATUS_REJECTED == auditStatus) {
                    SMSUtil.asyncSendSMS(doctor.getPhone(), SMSUtil.Template.AUDIT_FAIL_TEMPLATE, audit.getFailureReason());
                }
            } catch (Exception e) {
                LOGGER.error("医生ID:{} 认证审核通知异常", doctorId, e);
            }
            if (RedisUtil.keyOps().existsKey(CommonConstant.SUPERVISE_SYNCHRONIZE_KEY)) {
                // 如果备案成功，医生信息同步至监管
                if (Doctor.RECORD_STATUS_SUCCESS == status) {
                    RedisUtil.listOps().lpush(CommonConstant.SUPERVISE_PUSH_DOCTOR_LIST_KEY, doctorId + "");
                }
            }
        });

    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveRecordInfo(String operator, RecordParamAuditBO audit) throws BusinessException {
        Long doctorId = audit.getDoctorId();
        Doctor doctor = this.getDoctor(doctorId);
        Integer status = audit.getStatus();

        // 医生没有认证通过，不能审核备案
        if (Doctor.AUTH_STATUS_SUCCESS != doctor.getStatus()) {
            LOGGER.warn("医生没有认证通过，不能审核备案通过 doctorId:{}", doctorId);
            throw new BusinessException(ExceptionCodes.WITHDRAWALS_CERT_NOT_COMPLETED);
        }

        // 如果备案通过则校验资质
        LOGGER.info("认证通过校验资质信息  doctorId:{}", doctorId);
        this.validate(doctorId, Licence.DOCTOR_LICENCE_TYPE_MEDICAL, Licence.DOCTOR_LICENCE_TYPE_IDCARD);

        // 更新医生备案状态
        doctor.setRecordStatus(status);
        doctor.setChangedAt(new Date());
        doctorDao.update(doctor);

        Licence licence = licenceDao.getLicenceByType(doctorId, Licence.DOCTOR_LICENCE_TYPE_IDCARD);

        // 备案审核通过，通知电子签名服务注册医生CA账户
        if (Doctor.RECORD_STATUS_SUCCESS == status) {
            String pwd = licence.getNumber().substring(licence.getNumber().length() - 4).toUpperCase();
            esignFeginClient.esignDoctorAdd(doctorId, doctor.getName(),
                    licence.getNumber(), doctor.getPhone(), HOSPITAL_NAME, doctor.getSealImage(), pwd);
            //默认设置免密，身份证后四位
            // esignFeginClient.setNoSecret(doctorId, true, licence.getNumber().substring(licence.getNumber().length() -
            // 4, licence.getNumber().length()).toUpperCase());

            // 集成默认设置免密，身份证后四位
            // this.setNoSecret(doctorId, licence.getNumber()
            // .substring(licence.getNumber().length() - 4, licence.getNumber().length()).toUpperCase());
        }
        // 添加操作日志
        this.addRecordLog(doctorId, status, operator, audit.getFailureReason());

        // 异步处理
        taskExecutor.execute(() -> {
            // 备案通知
            try {
                notifyService.notifyRecordStatusChange(doctorId, status, audit.getFailureReason());
            } catch (BusinessException e) {
                LOGGER.error("医生ID:{} 备案通知异常", doctorId, e);
            }
        });
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void esignAuth(Long doctorId, Integer status, String reason) throws BusinessException {
        // 审核不通过
        if (ESIGN_AUTH_STATUS_NOTPASS.equals(status)) {
            RecordParamAuditBO auditBO = new RecordParamAuditBO();
            auditBO.setDoctorId(doctorId);
            auditBO.setStatus(3);
            auditBO.setFailureReason(reason);
            this.saveRecordInfo("CA认证系统", auditBO);
            LOGGER.info("电子签名医生认证不通过, doctorId:{}, reason:{}", doctorId, reason);
        } else {
            LOGGER.info("电子签名医生认证通过, doctorId:{}", doctorId);
        }
    }

    /**
     * 资质校验
     *
     * @param doctorId 医生ID
     * @param types    资质类型
     * @throws BusinessException 失败异常
     */
    private Map<Integer, Licence> validate(Long doctorId, Integer... types) throws BusinessException {
        // 缓存查询资质信息
        Map<Integer, Licence> licenceCatch = licenceDao.getLicenceByDoctorId(doctorId, types);

        for (int type : types) {
            switch (type) {
                // 执业资格证
                case Licence.DOCTOR_LICENCE_TYPE_MEDICAL:
                    Licence medicalLicence = licenceCatch.get(Licence.DOCTOR_LICENCE_TYPE_MEDICAL);
                    if (medicalLicence == null) {
                        LOGGER.warn("医生备案信息缺少执业资格证信息,doctorId:{}", doctorId);
                        throw new BusinessException(ExceptionCodes.CMS_DR_LICENC_FILE_WRONG);
                    }

                    // 执业资格证号
                    String medicalLicenceNumber = medicalLicence.getNumber();
                    validateMedicalLicenceNumber(medicalLicenceNumber);

                    // 执业资格证图片
                    String medicalLicenceUrl = medicalLicence.getUrl();
                    if (StringUtils.isEmpty(medicalLicenceUrl)) {
                        LOGGER.warn("医生备案信息缺少执业资格证信息,doctorId:{}", doctorId);
                        throw new BusinessException(ExceptionCodes.CMS_DR_LICENC_FILE_WRONG);
                    }
                    break;

                // 身份证
                case Licence.DOCTOR_LICENCE_TYPE_IDCARD:
                    Licence idCard = licenceCatch.get(Licence.DOCTOR_LICENCE_TYPE_IDCARD);
                    if (idCard == null) {
                        throw new BusinessException(ExceptionCodes.CMS_DR_RECORD_ID_FILE_WRONG);
                    }

                    String number = idCard.getNumber();
                    if (StringUtils.isEmpty(number)) {
                        throw new BusinessException(ExceptionCodes.CMS_DR_RECORD_ID_NUMBER_WRONG);
                    }
                    if (!IdcardUtil.isValidCard(number)) {
                        LOGGER.warn("医生备案信息身份证号错误,doctorId:{}", doctorId);
                        throw new BusinessException(ExceptionCodes.CARD_ID_NUM_INVALID);
                    }

                    String url = idCard.getUrl();
                    if (StringUtils.isEmpty(url) || url.split(",").length != 2) {
                        LOGGER.warn("医生备案信息身份证照片不完整,doctorId:{}", doctorId);
                        throw new BusinessException(ExceptionCodes.CMS_DR_RECORD_ID_FILE_WRONG);
                    }
                    break;

                // 个人照片
                case Licence.DOCTOR_LICENCE_TYPE_AVATAR:
                    Licence avatar = licenceCatch.get(Licence.DOCTOR_LICENCE_TYPE_AVATAR);
                    if (avatar == null || StringUtils.isEmpty(avatar.getUrl())) {
                        LOGGER.warn("医生认证个人照片不存在,doctorId:{}", doctorId);
                        throw new BusinessException(ExceptionCodes.PARAM_ERROR_AUTH_FILE_EMPTY);
                    }

                    // 工作证
                case Licence.DOCTOR_LICENCE_TYPE_EMCARD:
                    Licence emcard = licenceCatch.get(Licence.DOCTOR_LICENCE_TYPE_AVATAR);
                    if (emcard == null || StringUtils.isEmpty(emcard.getUrl())) {
                        LOGGER.warn("医生认证个人照片不存在,doctorId:{}", doctorId);
                        throw new BusinessException(ExceptionCodes.PARAM_ERROR_AUTH_FILE_EMPTY);
                    }
                default:
            }
        }
        return licenceCatch;
    }

    /**
     * 医生备案日志记录
     *
     * @param doctorId      医生ID
     * @param recordStatus  备案状态
     * @param operator      操作人
     * @param failureReason 审核不通过原因
     */
    private void addRecordLog(long doctorId, int recordStatus, String operator, String failureReason) {
        DrAccountOperateLog drAccountOperateLog = new DrAccountOperateLog();
        drAccountOperateLog.setDoctorId(doctorId);
        drAccountOperateLog.setStatus(recordStatus);

        // 审核失败
        if (Doctor.RECORD_STATUS_FAIL == recordStatus) {
            drAccountOperateLog.setReason(failureReason);
        }
        drAccountOperateLog.setCreatedAt(new Date());
        drAccountOperateLog.setCreatedBy(operator);

        accountOperateLogDao.save(drAccountOperateLog);
        LOGGER.info("操作人:{} 医生ID:{} 备案日志记录成功  {} ", operator, doctorId, recordStatus);
    }

    /**
     * 获取医生信息
     *
     * @param doctorId 医生ID
     */
    private Doctor getDoctor(Long doctorId) throws BusinessException {
        Doctor doctor = doctorDao.get(doctorId);
        if (doctor == null) {
            LOGGER.warn("医生不存在 ID:{}", doctorId);
            throw new BusinessException(ExceptionCodes.USER_NOT_EXIST);
        }

        // 账户已禁用
        if (PropertyValueConstants.PATIENT_ACCOUNT_STATUS_DISABLED == doctor.getAccountStatus()) {
            throw new BusinessException(ExceptionCodes.USER_NOT_NOMAL);
        }
        return doctor;
    }

    /**
     * 校验执业资格证号长度
     *
     * @param medicalLicenceNumber 执业资格证号
     * @throws BusinessException 该医生正在进行互联网医院备案审核，请输入执业资格证号码
     * @Author 王泽浩
     * @Date 2017年08月21日
     * @since 2.6.9
     */
    public static void validateMedicalLicenceNumber(String medicalLicenceNumber) throws BusinessException {
        // 执业资格证号
        if (StringUtils.isEmpty(medicalLicenceNumber)) {
            LOGGER.warn("医生备案信息缺少执业资格证信息");
            throw new BusinessException(ExceptionCodes.CMS_DR_LICENC_NUMBER_WRONG);
        }

        int length = medicalLicenceNumber.length();
        if (length == 0 || length > 27) {
            LOGGER.warn("医生职业资格证号格式不对,medicalLicenceNumber:{} length:{}", medicalLicenceNumber, length);
            throw new BusinessException(ExceptionCodes.CMS_DR_LICENC_NUMBER_WRONG);
        }
    }

    /**
     * 集成默认设置免密
     * 
     * @param doctorId
     * @param authPwd
     * @throws BusinessException
     */
    private void setNoSecret(Long doctorId, String authPwd) throws BusinessException {
        PreSignResultBO signResultBO = esignFeginClient.preNoSecret(doctorId);
        esignFeginClient.setNoSecret(NoSecretParamBO.builder().signUserId(doctorId).noSecret(true).authPwd(authPwd)
            .uniqueId(signResultBO.getUniqueId()).build());
    }
}
